/**
 *  StaDiUM - State Diagram Unified Modeler
 *  Copyright (C) 2007  Stefan Bucur, Mihai Balan, Claudiu Tanase
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.

 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 *  File Information
 *
 *  Last Modified On: $Date: 2007-06-14 11:06:27 +0000 (Thu, 14 Jun 2007) $
 *  Last Modified By: $Author: stefan.bucur $
 */

package ro.pub.cs.stadium.platform;


import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.CoolBar;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolItem;

import ro.pub.cs.stadium.document.IDocument;
import ro.pub.cs.stadium.document.IDocumentItem;
import ro.pub.cs.stadium.document.IState;
import ro.pub.cs.stadium.document.ITransition;
import ro.pub.cs.stadium.document.Point;
import ro.pub.cs.stadium.document.Size;
import ro.pub.cs.stadium.document.logic.Document;
import ro.pub.cs.stadium.document.logic.ExpressionException;
import ro.pub.cs.stadium.document.logic.State;
import ro.pub.cs.stadium.document.logic.Transition;
import ro.pub.cs.stadium.document.logic.VariableList;
import ro.pub.cs.stadium.output.verilog.VerilogOutputPlugin;
import ro.pub.cs.stadium.platform.general.InputDialog;
import ro.pub.cs.stadium.plugin.IDocumentPlugin;
import ro.pub.cs.stadium.plugin.IOutputPlugin;
import ro.pub.cs.stadium.testing.BasicState;
import ro.pub.cs.stadium.testing.BasicTransition;

/**
 * Encapsulates the functionality of the Platform Main Window.
 * @author stefan
 *
 */
public class MainWindow implements SelectionListener, DrawingEventListener {
	
	// The minimum size of a window that a splitter could redimension
	private final int SPLIT_SIZE_LIMIT = 30; // TODO: make it configurable
	
	private Shell shell = null;
	
	private Menu menuBar = null;
	private CoolBar coolBar = null;
	
	// Managers
	private MenuManager menuManager = null;
	private ToolBarManager toolManager = null;
	private final PluginManager pluginManager;
	private final SettingsManager settingsManager;
	
	// The container of all the tool windows (property windows,
	// selected tool settings, color pickers, etc)
	private ToolWndContainer toolWndContainer = null;
	
	private Sash wndContSplitter = null;
	private FormData splitterData = null; 
	
	// The all mighty drawing surface (the document view)
	private DrawingCanvas drawingCanvas = null; 
	
	
	private Label statusLine = null;
	
	/**
	 * The current active document
	 */
	
	private IDocument theDocument = null;
	
	/**
	 * Changes have occured without saving
	 */
	private boolean dirty = false;
	
	private String fileName = null;
	
	// MENU EVENTS
	
	/**
	 * Triggered when a menu selection takes place. 
	 */
	private void menuSelected(SelectionEvent e) {
//		 Showing the About box
		if (e.widget == menuManager.getAboutMenuItem()) {
			AboutWindow aboutWindow = new AboutWindow(shell);
			aboutWindow.showModal();
		}
		// Showing the Preferences dialog
		if (e.widget == menuManager.getPreferencesMenuItem()) {
			SettingsWindow settingsWindow = new SettingsWindow(shell);
			settingsWindow.showModal();
		}
		// Showing the Plugins dialog
		if (e.widget == menuManager.getPluginsMenuItem()) {
			PluginWindow pluginsWindow = new PluginWindow(this);
			pluginsWindow.showModal();
		}
		// Exiting the application
		if (e.widget == menuManager.getExitMenuItem()) {
			shell.close();
		}
		// Opening a new document
		if (e.widget == menuManager.getOpenMenuItem()) {
			FileDialog openDialog = new FileDialog(shell, SWT.OPEN);
			openDialog.setText("Select a diagram to open");
			
			String fileName = openDialog.open();
			
			// TODO: implement document opening
		}
		
		if (e.widget == menuManager.getNewMenu()) {
			loadDocument(new Document());
		}
		
		if (e.widget == menuManager.getExportMenuItem()) {
			FileDialog saveDialog = new FileDialog(shell, SWT.SAVE);
			saveDialog.setText("Select a Verilog file name");
			
			String fileName = saveDialog.open();
			
			if (fileName != null) {
				try {
					FileOutputStream fos = new FileOutputStream(fileName);
					
					VerilogOutputPlugin outputPlug = new VerilogOutputPlugin();
					
					outputPlug.writeDocument(theDocument, fos);
					
					fos.close();
				} catch (FileNotFoundException ex) {
					MessageBox mBox = new MessageBox(shell, SWT.OK);
					
					mBox.setText("File could not be written!");
					
					mBox.open();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
			
		}
	}
	
	private void toolSelected(SelectionEvent e) {
		if (e.widget == toolManager.getNewToolItem()) {
			if (e.detail == SWT.ARROW) {
				Rectangle rect = ((ToolItem)e.widget).getBounds();
				
				org.eclipse.swt.graphics.Point pt = 
					new org.eclipse.swt.graphics.Point(rect.x, rect.y + rect.height);
				
				Menu newMenu = toolManager.getNewPopupMenu();
				
				pt = toolManager.getPlatformToolbar().toDisplay(pt);
				
				newMenu.setLocation(pt);
				newMenu.setVisible(true);
			}
		}
		
		if (e.widget == toolManager.getSaveToolItem()) {
			saveDocument();
		}
		
		if (e.widget == toolManager.getZoomCombo()) {
			updateZoom();
		}
		
		if (e.widget == toolManager.getSelectToolItem()) {
			drawingCanvas.setCanvasMode(CanvasMode.SELECTING);
		}
		
		if (e.widget == toolManager.getPanToolItem()) {
			drawingCanvas.setCanvasMode(CanvasMode.PANNING);
		}
		
		if (e.widget == toolManager.getNewStateToolItem()) {
			drawingCanvas.setCanvasMode(CanvasMode.ADD_STATE);
		}
		
		if (e.widget == toolManager.getNewTransToolItem()) {
			drawingCanvas.setCanvasMode(CanvasMode.ADD_TRANSITION);
		}
	}
	
	// PLUGIN MANAGEMENT ROUTINES
	
	private void initPlugins() {
		IDocumentPlugin[] docPlugins = pluginManager.getDocumentPlugins();
		
		for (IDocumentPlugin crPlugin : docPlugins) {
			crPlugin.registerNewMenus(menuManager.getNewMenu());
		}
	}
	
	// DOCUMENT MANAGEMENT ROUTINES
	

	
	public void loadDocument(IDocument document) {
		if (theDocument != null) {
			unloadDocument();
		}
		
		this.theDocument = document;
		
		dirty = false;
		
		drawingCanvas.setDocument(document);
	}
	
	private void unloadDocument() {
		
	}
	
	public void saveDocument() {
		IOutputPlugin xmlPlugin = pluginManager.getOutputPlugin("XML");
		
		if (fileName == null) {
			
			FileDialog saveDialog = new FileDialog(shell, SWT.SAVE);
			saveDialog.setText("Select an XML file name");
			
			fileName = saveDialog.open();
		}
		
		if (fileName != null) {
			try {
				FileOutputStream fos = new FileOutputStream(fileName);
				
				xmlPlugin.writeDocument(theDocument, fos);
				
				fos.close();
			} catch (FileNotFoundException ex) {
				MessageBox mBox = new MessageBox(shell, SWT.OK);
				
				mBox.setText("File could not be written!");
				
				mBox.open();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		
		
	}
	
	public void openDocument() {
		
	}
	
	// USER INTERFACE ROUTINES
	
	/**
	 * Populates the zoom combo box
	 */
	private void populateZoomCombo() {
		Combo zoomCombo = toolManager.getZoomCombo();
		
		zoomCombo.setItems(new String[] {
				"25%",
				"50%",
				"75%",
				"100%",
				"200%",
				"400%"
		});

		zoomCombo.select(3); // 100 %
	}
	
	/**
	 * Updates the document zoom based on the zoom combo text value
	 *
	 */
	private void updateZoom() {
		Combo zoomCombo = toolManager.getZoomCombo();
		
		String zoomText = zoomCombo.getText();
		int perIndex = zoomText.indexOf('%');
		
		if (perIndex>=0) {
			zoomText = zoomText.substring(0, perIndex);
		}
		
		zoomText = zoomText.trim();
		
		try {
			int per = Integer.parseInt(zoomText);
			theDocument.setZoom((float)per/100);
			drawingCanvas.redraw();
			
		} catch (NumberFormatException ex) {
			// Do nothing
		}
	}
	
	/**
	 * Creates and populates the main menu
	 *
	 */
	private void createMainMenu() {
		menuBar = new Menu(shell, SWT.BAR);
		
		shell.setMenuBar(menuBar);
		
		// We create the menu manager
		menuManager = new MenuManager(menuBar, this);
		menuManager.populateMenuBar();
		
	}
	
	/**
	 * Creates and populates the tool bar
	 *
	 */
	private void createToolBar() {
		coolBar = new CoolBar(shell, SWT.FLAT);
		
		
		
		FormData coolData = new FormData();
		coolData.left = new FormAttachment(0);
		coolData.right = new FormAttachment(100);
		coolData.top = new FormAttachment(0);
		
		coolBar.setLayoutData(coolData);
		
		
		// We create the toolbar manager
		toolManager = new ToolBarManager(coolBar, this);
		toolManager.populateToolBar();
		
		populateZoomCombo();
		
		coolBar.setLocked(true);
		coolBar.layout();
		coolBar.pack();
		
		// Make select the default tool
		toolManager.getSelectToolItem().setSelection(true);
	}
	
	/**
	 * Creates and populates the entire Graphical User Interface
	 *
	 */
	private void populateGUI() {
		createMainMenu();
		createToolBar();
		
		statusLine = new Label(shell, SWT.SHADOW_IN | SWT.HORIZONTAL |
				SWT.LEFT);
		
		FormData statusData = new FormData();
		statusData.left = new FormAttachment(0);
		statusData.right = new FormAttachment(100);
		statusData.bottom = new FormAttachment(100);
		
		statusLine.setLayoutData(statusData);
		
		toolWndContainer = new ToolWndContainer(shell, SWT.NONE);
		wndContSplitter = new Sash(shell, SWT.VERTICAL | SWT.SMOOTH);
		drawingCanvas = new DrawingCanvas(shell, SWT.NONE);
		
		drawingCanvas.addDrawingEventListener(this);
		
		// Placing the tool window container
		
		FormData toolData = new FormData();
		toolData.left = new FormAttachment(0);
		toolData.top = new FormAttachment(coolBar);
		toolData.bottom = new FormAttachment(statusLine);
		toolData.right = new FormAttachment(wndContSplitter);
		toolWndContainer.setLayoutData(toolData);
		
		// Placing the splitter
		
		splitterData = new FormData();
		splitterData.left = new FormAttachment(30);
		splitterData.top = new FormAttachment(coolBar);
		splitterData.bottom = new FormAttachment(statusLine);
		wndContSplitter.setLayoutData(splitterData);
		wndContSplitter.addSelectionListener(this);
		
		// Placing the drawing canvas
		
		FormData canvasData = new FormData();
		canvasData.left = new FormAttachment(wndContSplitter);
		canvasData.right = new FormAttachment(100);
		canvasData.top = new FormAttachment(coolBar);
		canvasData.bottom = new FormAttachment(statusLine);
		drawingCanvas.setLayoutData(canvasData);
	}

	/**
	 * The constructor
	 * @param display
	 */
	public MainWindow(Display display) {
		shell = new Shell(display);
		shell.setText("StaDiUM");
		
		pluginManager = new PluginManager(this);
		settingsManager = new SettingsManager();
		
		FormLayout layout = new FormLayout();
		shell.setLayout(layout);
		
		populateGUI();
	}
	
	/**
	 * 
	 * @return the main window Shell object
	 */
	public Shell getShell() {
		return shell;
	}
	
	public IDocument getDocument() {
		return theDocument;
	}
	
	public PluginManager getPluginManager() {
		return pluginManager;
	}
	
	public SettingsManager getSettingsManager() {
		return settingsManager;
	}
	
	public MenuManager getMenuManager() {
		return menuManager;
	}
	
	public ToolBarManager getToolBarManager() {
		return toolManager;
	}
	
	// EVENT HANDLERS
	
	// SelectionListener Interface

	public void widgetDefaultSelected(SelectionEvent e) {
		if (e.widget == toolManager.getZoomCombo()) {
			updateZoom();
		}
		
	}

	/**
	 * Occurs when a widget on the main window is selected
	 */
	public void widgetSelected(SelectionEvent e) {
		menuSelected(e);
		toolSelected(e);
		
		// Dragging the splitter
		if (e.widget == wndContSplitter) {
			Rectangle sashRect = wndContSplitter.getBounds();
			Rectangle shellRect = shell.getClientArea();
			
			int right = shellRect.width - sashRect.width - SPLIT_SIZE_LIMIT;
			
			e.x = Math.max(Math.min(right, e.x), SPLIT_SIZE_LIMIT);
			
			if (e.x != sashRect.x) {
				splitterData.left = new FormAttachment(0, e.x);
				shell.layout();
			}
		}
		
		
	}
	
	// DrawingEventListener

	public void transitionAdded(IState start, IState end) {
		
		InputDialog inputDlg = new InputDialog(shell);
		inputDlg.setQuestion("Enter a name for the new transition:");
		if (inputDlg.showModal() == 0) {
			Transition newTrans = new Transition(inputDlg.getAnswer());
			newTrans.setStartPoint(start);
			newTrans.setEndPoint(end);
			
			newTrans.setLabelSize(new Size(3.0f, 1.0f));
			
			Point startPos = start.getPosition();
			Point endPos = end.getPosition();
			
			float len = (float)Math.sqrt(
					(startPos.coords[0] - endPos.coords[0]) * (startPos.coords[0] - endPos.coords[0]) +
					(startPos.coords[1] - endPos.coords[1]) * (startPos.coords[1] - endPos.coords[1])
				);
			
			newTrans.setStartControlPoint(new Point(
					2*(endPos.coords[0] - startPos.coords[0])/len,
					2*(endPos.coords[1] - startPos.coords[1])/len));
			
			newTrans.setEndControlPoint(new Point(
					-2*(endPos.coords[0] - startPos.coords[0])/len,
					-2*(endPos.coords[1] - startPos.coords[1])/len));
			
			theDocument.getTransitions().put(newTrans.getName(), newTrans);
			drawingCanvas.redraw();
		}
	}

	public void stateAdded(Point location) {
		InputDialog inputDlg = new InputDialog(shell);
		inputDlg.setQuestion("Enter a name for the new state:");
		
		if (inputDlg.showModal() == 0) {
			State newState = new State(inputDlg.getAnswer(),
					new VariableList());
			
			newState.setPosition(location);
			newState.setContentSize(new Size(4.0f, 1.0f));
			
			theDocument.getStates().put(newState.getName(), newState);
			drawingCanvas.redraw();
		}
		
	}

	public void itemDoubleClicked(IDocumentItem item) {
		InputDialog inputDlg = new InputDialog(shell);
		
		if (item instanceof IState)
			inputDlg.setQuestion("Enter the actions to take in this state:");
		if (item instanceof ITransition)
			inputDlg.setQuestion("Enter the logic condition for the transition:");
			
		if (inputDlg.showModal() == 0) {
			if (item instanceof State) {
				State state = (State) item;
				try {
					state.setActions(inputDlg.getAnswer());
				} catch (ExpressionException ex) {
					ex.printStackTrace();
				}
			}
			
			if (item instanceof Transition) {
				Transition trans = (Transition) item;
				try {
					trans.setCondition(inputDlg.getAnswer());
				} catch (ExpressionException ex) {
					ex.printStackTrace();
				}
			}
			
			drawingCanvas.redraw();
		}
	}

	public void itemDelete(IDocumentItem item) {
		MessageBox mBox = new MessageBox(shell, SWT.YES | SWT.NO | SWT.ICON_QUESTION);
		mBox.setText("Are you sure?");
		mBox.setMessage("Do you want to erase item " + item.getName() + " ?");
		
		if (mBox.open() == SWT.YES) {
			if (item instanceof IState) {
				IState state = (IState)item;
				for (ITransition crTrans : state.getInTransitions().values()) {
					crTrans.setStartPoint(null);
					
					
					theDocument.getTransitions().remove(crTrans.getName());
				}
				
				for (ITransition crTrans : state.getOutTransitions().values()) {
					
					crTrans.setEndPoint(null);
					
					theDocument.getTransitions().remove(crTrans.getName());
				}
				
				theDocument.getStates().remove(state.getName());
			}
			
			if (item instanceof ITransition) {
				theDocument.getTransitions().remove(item.getName());
			}
			
			drawingCanvas.redraw();
		}
		
	}
}
